package ArithmeticLibrary.Sets;

import java.math.BigInteger;
import java.util.List;

import ArithmeticLibrary.Elements.ElementGroup;
import ArithmeticLibrary.Elements.ElementMultiplicativeGroup;
import ArithmeticLibrary.Util.ArrayOfElementGroup;
import ArithmeticLibrary.Util.BigIntegerModulo;
import ByteTree.ByteTree;
import ByteTree.Node;
import ByteTree.Leaf;
import Cryptographic.PRG;

/**
 * This class represents a basic multiplicative group, generated from a field Z_p. 
 * Therefore it is defined by the order and generator of the group, and the prime order of Z_p.
 * 
 * @author Lea Golan
 *
 */
public class MultiplicativeGroup extends Group{
	
	private BigIntegerModulo p; //prime order of the underlying field Z_p.
	private BigInteger q; //prime order of the group.
	private ElementMultiplicativeGroup generator; //generator g of the group G i.e. G={g^n | n is in N}.
	private ElementMultiplicativeGroup unitElement; // holds 1_Gq
	
	/**
	 * Constructs a new multiplicative group with respect to the parameters.
	 *  
	 * @param p - prime order of the underlying field
	 * @param q - prime group order
	 * @param g - a generator of the group
	 * @throws Exception if p or q are not positive prime numbers
	 */
	public MultiplicativeGroup(java.math.BigInteger p, java.math.BigInteger q, 
									java.math.BigInteger g) throws Exception{
		if ((p.compareTo(BigInteger.ZERO) != 1) || 
			(q.compareTo(BigInteger.ZERO) != 1)){
			throw new Exception("BuildError MultiplicaticeGroup: " +
					"both p and q must be positive primes");
		}
		this.p = new BigIntegerModulo(p);
		this.q = q;
		this.generator = new ElementMultiplicativeGroup(g, this);
		// g^q = 1 (mod p) Lagrange 
		this.unitElement = new ElementMultiplicativeGroup(this.p.powModulo(g, q), this);
	}

	/**
	 * t is of the form node(p',q',g',(bytes)k(e)) 
	 * where p' ,q' ,g' are byte trees representing p,q,g respectively.
	 * It calls MultiplicativeGroup(p, q, g)

	 * @param t a byte tree of the above form
	 * @throws Exception if the byte tree structure is different from what we described
	 */
	public MultiplicativeGroup(ByteTree t) throws Exception{
		Node root = t.getRoot();
		if ((root.isLeaf() == true) || (root.getNumChildren() != 4)){
			throw new Exception("ByteTree structure is inconsistent with " +
					"ElementMultiplicativeGroup");
		}
		
		List<Node> children = root.getChildren();
		
		if ((children.get(0).isLeaf() == false) || 
			(children.get(1).isLeaf() == false) ||
			(children.get(2).isLeaf() == false)){
			// we don't check the forth child because we don't deal with it
			throw new Exception("ByteTree structure is inconsistent with" +
					" ElementMultiplicativeGroup");
		}
		
		this.p = new BigIntegerModulo( new BigInteger(((Leaf) children.get(0)).getData()) );
		this.q = new BigInteger( ((Leaf) children.get(1)).getData() );
		
		if ((p.getPModulo().compareTo(BigInteger.ZERO) != 1) || 
			(q.compareTo(BigInteger.ZERO) != 1)){
			throw new Exception("BuildError MultiplicaticeGroup: " +
					"both p and q must be positive primes");
		}
		
		try {
			this.generator = createElement(new ByteTree(children.get(2)));
		} catch (Exception e) {
			throw new Exception("Build MultiplicativeGroup failed:\n"+e.getMessage());
		}
		
		// g^q = 1 (mod p) Lagrange 
		this.unitElement = new ElementMultiplicativeGroup(
				this.p.powModulo(this.generator.getElementValue(), q), this);
	}
	
	/**
	 * BigIntegerModulo is a class for computing operations on BigInteger modulo a prime number: p
	 * @return the field p
	 */
	public BigIntegerModulo getPClass(){
		return this.p;
	}
	
	/**
	 * Checks if the specified element is in the group, meaning the element is a non-negative number, 
	 * lower than the group order and meets a criterion to be in the group.
	 * 
	 * @param element - an element of a group
	 * @return true iff the given element belongs to the group
	 */
	@Override
	public boolean belong(ElementGroup element) {
		return this.equals(element.getGroup());
	}

	/**
	 * Creates a new Group element by encoding message into the group. 
	 * 
	 * @param message - a byte tree of the form leaf(bytes_k(a))
	 * @return a new ElementMultiplicativeGroup according to a extracted from message 
	 * @throws Exception - if the byte tree is not appropriately formatted
	 */
	@Override
	public ElementMultiplicativeGroup createElement(ByteTree message) throws Exception {
		try {
			return new ElementMultiplicativeGroup(message, this);
		} catch (Exception e) {
			throw new Exception("create element failed:\n"+e.getMessage());
		}
	}

	/**
	 * Compare this to obj
	 * 
	 */
	public boolean	equals(java.lang.Object obj) {
		if (obj == this){
            return true;
        }
        if (!(obj instanceof MultiplicativeGroup)){
            return false;
        }
        
        MultiplicativeGroup other = (MultiplicativeGroup) obj;
        return ( (this.p.equals(other.p)) &&
        		 (this.q.equals(other.q)) &&
        		 (this.generator.getElementValue().equals(other.generator.getElementValue())) &&
        		 (this.unitElement.getElementValue().equals(other.unitElement.getElementValue()))  );
	}
	
	/**
	 * @return the underlying field order
	 */
	@Override
	public BigInteger getSuperGroupOrder() {
		return this.p.getPModulo();
	}

	/**
	 * @return the group generator 
	 */
	@Override
	public ElementMultiplicativeGroup getGroupGenerator() {
		return this.generator;
	}

	/**
	 * @return the group identity element 
	 */
	@Override
	public ElementMultiplicativeGroup getGroupIdentityElement() {
		return this.unitElement;
	}

	/**
	 * @return the group order 
	 */
	@Override
	public BigInteger getGroupOrder() {
		return this.q;
	}

	/**
	 * Generates a random array of elements belonging to the group.
	 * These elements are all generators since necessarily the group order is prime
	 * 
	 * @param arraySize - the number of generators requested
	 * @param pseudoGenerator - a PRG that generates random numbers
	 * @param seed - seed to give to the PRG
	 * @param error - the size of the statistical difference between two numbers
	 * @return an array of generators belonging to the group
	 * @throws Exception if arraySize is bigger than the group size or if PRG(seed) failed 
	 */
	@Override
	public ArrayOfElementGroup randomArray(int arraySize, PRG pseudoGenerator, byte[] seed,
			int error) throws Exception {
		if (this.q.compareTo(BigInteger.valueOf(arraySize)) == -1){
			throw new Exception("error in generating random multiplicativeGroup elements:\n" +
					"number of requested random group elements exceeds group size");
		}
		
		ArrayOfElementGroup retArr = new ArrayOfElementGroup(arraySize, this);
				
		int np = this.p.getPModulo().bitLength();
		BigInteger randOutMod;
		int len = np + error;
		
		int NrNp = (len + 7) / 8;
		byte[] tiArray;
		byte[] extendedTiArray;
		BigInteger ti;
		try {
	
			for(int i=0; i < arraySize; i++){
				tiArray = pseudoGenerator.compute(seed, (i*NrNp), ((i+1)*NrNp) - 1);
				extendedTiArray = new byte[tiArray.length + 1];
				extendedTiArray[0] = 0x00;
				System.arraycopy(tiArray, 0, extendedTiArray, 1, tiArray.length);			
				
				
				if ((len % 8) > 0) {
					extendedTiArray[1] &= (0xFF >> (8 - (len % 8)));
				}
				
				
				ti = new BigInteger(extendedTiArray);
				// map each randOutput number to be a number between zero and twoPow-1
				randOutMod = ti.mod(getTwoPow(np, error)); //.mod(getSuperGroupOrder());
				// computes the mapping between a number modulo twoPow to a number modulo p
				randOutMod = this.p.powModulo(randOutMod, getExponentForMapping());
				retArr.set(i, new ElementMultiplicativeGroup(randOutMod, this));
			}
		} catch (Exception e) {
			throw new Exception("error in generating random multiplicativeGroup elements:\n"
					+e.getMessage());
		}
		
		return retArr;
	}


	private BigInteger getExponentForMapping() {
		BigInteger num = this.p.getPModulo().subtract(BigInteger.ONE); 
		BigInteger denum = this.q;
		return num.divide(denum);
	}

	private BigInteger getTwoPow(int np, int nr) {
		return BigInteger.valueOf(2).pow(np+nr);
	}

}
